#include <54func.h>
typedef struct Conn_s{
    int isConnected;//0 client未连接 1 client已经连接
    int netfd;//存储不同客户端的通信socket接口
    time_t lastActive;//记录客户端连接到服务器的时间

}Conn_t;
int main(int argc,char *argv[])
{
    // ./0_azhen 192.168.187.128 1234
    ARGS_CHECK(argc,3);

    //创建socket套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);

    //初始化
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

    //允许该套接字的端口在关闭后立即被重新使用。
    int reuse = 1;
    //SOL_SOCKET：表示在套接字级别设置选项
    //SO_REUSEADDR：是一个常用的套接字选项，用于允许端口重用
    int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
    ERROR_CHECK(ret,-1,"setsockopt");

    //bind：将socket与网络ip以及端口进行绑定
    ret = bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
    ERROR_CHECK(ret,-1,"bind");

    //listen：监听（将socket变为被动，用于与客户端进行连接，同时将接收和发送缓冲区变为全连接队列和半连接队列）
    listen(sockfd,50);

    ////accept：从全连接队列中取出连接，并创建一个新的socket对象，会分配新的文件描述符
    //int netfd = accept(sockfd,NULL,NULL);//这里不返回客户端的ip以及端口，因此NULL
    //ERROR_CHECK(netfd,-1,"accept");

//实现：aqiang与azhen的即时通信!!!!!!!!
    //fd_set rdset;
    //char buf[4096] = {0};
    //while(1)
    //{
    //    FD_ZERO(&rdset);
    //    FD_SET(STDIN_FILENO,&rdset);
    //    FD_SET(netfd,&rdset);
    //    select(netfd+1,&rdset,NULL,NULL,NULL);
    //    if(FD_ISSET(STDIN_FILENO,&rdset))
    //    {
    //        //azhen通过标准输入发送消息给aqiang
    //        bzero(buf,sizeof(buf));
    //        read(STDIN_FILENO,buf,sizeof(buf));
    //        int sret = send(netfd,buf,strlen(buf),0);//把从stdin中读取的消息发送给通信socket
    //        if(sret == 0)
    //        {
    //            //azhen主动断开连接
    //            printf("azhen主动断开连接！\n");
    //            break;
    //        }
    //    }
    //    if(FD_ISSET(netfd,&rdset))
    //    {
    //        //azhen接收aqiang发送的消息
    //        bzero(buf,sizeof(buf));
    //        int sret = recv(netfd,buf,sizeof(buf),0);
    //        if(sret == 0)
    //        {
    //            printf("aqiang主动断开连接！\n");
    //            break;
    //        }
    //        printf("aqiang：%s\n",buf);
    //    }
    //}

// 实现断线重新连接！！！！！！！！！
   // fd_set monitorSet;//监听集合
   // fd_set readySet;//就绪集合
   // FD_ZERO(&monitorSet);
   // FD_SET(sockfd,&monitorSet);
   // 
   // int netfd = -1;//netfd为-1，表示阿强不存在
   // char buf[4096] = {0};
   // while(1)
   // {
   //     memcpy(&readySet,&monitorSet,sizeof(monitorSet));
   //     select(20,&readySet,NULL,NULL,NULL);
   //     
   //     //建立新连接
   //     if(FD_ISSET(sockfd,&readySet))
   //     {
   //         netfd = accept(sockfd,NULL,NULL);
   //         printf("aqiang 已经连接！\n");
   //         //aqiang、azhen建立连接,接下来只需要监听stdin和netfd即可
   //         FD_CLR(sockfd,&monitorSet);
   //         FD_SET(STDIN_FILENO,&monitorSet);
   //         FD_SET(netfd,&monitorSet);
   //         
   //     }
   //     if(FD_ISSET(STDIN_FILENO,&readySet))
   //     {
   //         //读取消息，写到socket中
   //         bzero(buf,sizeof(buf));
   //         int sret = read(STDIN_FILENO,buf,sizeof(buf));
   //         if(sret == 0)
   //         {
   //             //azhen主动断开连接
   //             FD_SET(sockfd,&monitorSet);
   //             FD_CLR(STDIN_FILENO,&monitorSet);
   //             FD_CLR(netfd,&monitorSet);
   //             close(netfd);
   //             netfd = -1;
   //             printf("azhen主动断开连接！\n");
   //             continue;
   //         }
   //         send(netfd,buf,strlen(buf),0);
   //     }
   //     if(FD_ISSET(netfd,&readySet))
   //     {
   //         //写socket消息
   //         bzero(buf,sizeof(buf));
   //         int sret = recv(netfd,buf,sizeof(buf),0);
   //         if(sret == 0)
   //         {
   //             //aqiang主动断开连接
   //             FD_SET(sockfd,&monitorSet);//要用下次创建aqiang的连接
   //             FD_CLR(STDIN_FILENO,&monitorSet);
   //             FD_CLR(netfd,&monitorSet);
   //             close(netfd);
   //             netfd = -1;
   //             printf("aqiang主动断开连接！\n");
   //             continue;
   //         }
   //         printf("aqiang：%s\n",buf);
   //     }
   // }


//实现一个聊天室：即一个人发消息，其他人都能收到，这里服务器起到一个转发消息的作用
// 要在该聊天室内实现一个超时踢人的效果，即某个客户端超时5秒，则自动退出
    fd_set monitorSet;
    fd_set readySet;
    FD_ZERO(&monitorSet);
    FD_SET(sockfd,&monitorSet);
    //这里服务器的作用是用来建立连接，同时将多个客户端发送的消息转发给其它客户端
    //因此需要构建一个数据结构，用于管理服务端为客户端创建的通信socket，存储所有的netfd  -->  数组
    Conn_t client[1024];//存储所有的客户端连接，以及记录客户端连接的时间
    for(int i=0;i<1024;i++)
    {
        //初始化客户端状态：未连接
        client[i].isConnected = 0;
    }
    //接下来要想着对该数据结构如何进行增删改查
    int curidx = 0;//下一次加入的netfd的下标  --> 用于增加，删除则直接将其值置为-1
    //哈希查找
    int fdToidx[1024];
    for(int i=0;i<1024;i++)
    {
        fdToidx[i] = -1;
    }

    time_t now;//用于记录当下时间
    char buf[4096];
    while(1)
    {
        memcpy(&readySet,&monitorSet,sizeof(fd_set));
        struct timeval timeout;
        timeout.tv_sec = 1;//秒
        timeout.tv_usec = 0;//微秒
        select(1024,&readySet,NULL,NULL,&timeout);
        //客户端要创建连接
        now = time(NULL);//若是1秒内，没有任何文件描述符就绪，则超时  -> 每一秒超时打印
        printf("now = %s\n",ctime(&now));
        
        //有新的客户端建立连接
        if(FD_ISSET(sockfd,&readySet))
        {
            //创建新连接
            client[curidx].isConnected = 1;//标志着该客户端是处于连接状态
            client[curidx].netfd = accept(sockfd,NULL,NULL);
            //当客户端连接到服务器的时候，记录一下客户端的时间
            client[curidx].lastActive = time(NULL);
            printf("i = %d,netfd = %d,now = %s\n",curidx,client[curidx].netfd,ctime(&client[curidx].lastActive));
            //哈希查找，将数组的值与键相互调换
            fdToidx[client[curidx].netfd] = curidx;
            //将当前创建连接的通信socket加入到监听集合
            FD_SET(client[curidx].netfd,&monitorSet);//将加入连接的客户端，加入到就绪集合中
            ++curidx;//自增操作一定要放在后面，不然回影响前面的查找操作
        }
        
        //若是已经有连接了，那么无论是哪一个客户端发送消息，其它客户端都能接收到
        //一写多读
        for(int i=0;i<curidx;i++){//curidx代表加入的客户端数
            //若是某个客户端在连接，并且想要发送消息
            if(client[i].isConnected == 1 && FD_ISSET(client[i].netfd,&readySet))
            {
                bzero(buf,sizeof(buf));
                ssize_t sret = recv(client[i].netfd,buf,sizeof(buf),0);//从通信socket中读取数据
                if(sret == 0){
                    //若是对面的客户端断开连接
                    FD_CLR(client[i].netfd,&monitorSet);
                    close(client[i].netfd);
                    fdToidx[client[i].netfd] = -1;
                    client[i].isConnected = 0;//删除该客户端的连接
                    printf("有客户端断开连接！\n");
                    continue;
                }
                //若当前客户端就绪，则用于记录当前客户端发送消息的时间
                client[i].lastActive = time(NULL);
                printf("i = %d,netfd = %d,now = %s\n",curidx,client[i].netfd,ctime(&client[i].lastActive));

                //转发：将读取的数据写给其它所有的客户端
                for(int j = 0;j<curidx;j++)//这里是从头循环到最大客户端号
                {
                    if(client[j].isConnected == 1 && j !=i)//若是该客户端没有断开连接，并且不是自身，那么就将数据发哦是那个给他
                    {
                        send(client[j].netfd,buf,strlen(buf),0);
                    }
                }
            }
        }
        //踢人，五秒内没有响应的断开连接
        for(int i = 0;i<curidx;i++){
            if(client[i].isConnected == 1 && now-client[i].lastActive > 5){
                //踢人，五秒内没有任何操作，则将当前这个客户端踢掉
                FD_CLR(client[i].netfd,&monitorSet);
                close(client[i].netfd);
                fdToidx[client[i].netfd] = -1;
                client[i].isConnected = 0;//表示当前客户端断开连接
            }
        }
    }


    return 0;
}
